# -*- coding: utf-8 -*-

"""剑指 Offer 20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值（包括整数和小数）。

数值（按顺序）可以分成以下几个部分：
    若干空格
    一个 小数 或者 整数
    （可选）一个 'e' 或 'E' ，后面跟着一个 整数
    若干空格

小数（按顺序）可以分成以下几个部分：
    （可选）一个符号字符（'+' 或 '-'）
    下述格式之一：
    至少一位数字，后面跟着一个点 '.'
    至少一位数字，后面跟着一个点 '.' ，后面再跟着至少一位数字
    一个点 '.' ，后面跟着至少一位数字

整数（按顺序）可以分成以下几个部分：
    （可选）一个符号字符（'+' 或 '-'）
    至少一位数字

部分数值列举如下：
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]

部分非数值列举如下：
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]

示例 1：
输入：s = "0"
输出：true

示例 2：
输入：s = "e"
输出：false

示例 3：
输入：s = "."
输出：false

示例 4：
输入：s = "    .1  "
输出：true

提示：
1 <= s.length <= 20
s 仅含英文字母（大写和小写），数字（0-9），加号 '+' ，减号 '-' ，空格 ' ' 或者点 '.' 。"""

from enum import Enum

class Solution:
    """可选情况太多，存在无数边界情况。但是规则罗列清晰，可以利用编译原理中的词法和语法解析方案。
    
    SPACE :    ' '
    NUM :    [0-9]
    DOT :    '.'
    E :    [Ee]
    PLUSMINUS :    [+-]
    SEMICOLON : ';'

    program
        : SPACE* (floatl | intl) (E int)? SPACE* SEMICOLON
        ;

    floatl:
        : PLUSMINUS? floatabs
        ;

    floatabs
        :NUM+ DOT
        |NUM+ DOT NUM+
        |DOT NUM+
        ;

    intl:
        :PLUSMINUS? intabs
        ;

    intabs:
        :NUM+
        ;
    """
    def isNumber(self, s: str) -> bool:
        s += ';'
        try:
            tokens = Lexer.parse(s)
        except LexerError:
            return False
        syntaxer = Syntaxer(tokens)
        return syntaxer.program()


class TokenType(Enum):
    SPACE = 1
    NUM = 2
    DOT = 3
    E = 4
    PLUSMINUS = 5
    SEMICOLON = 6


class Token():
    def __init__(self, chr, tipe) -> None:
        self.chr = chr
        self.tipe = tipe


class LexerError(Exception):
    def __init__(self, *args: object) -> None:
        super().__init__(*args)


class Lexer():
    space = ' '
    nums = ('0','1','2','3','4','5','6','7','8','9')
    dot = '.'
    e = ('E', 'e')
    plusminus = ('+', '-')
    semicolon = ';'

    @classmethod
    def parse(cls, chrs):
        tokens = []
        for chr in chrs:
            if chr == cls.space:
                tokens.append(Token(chr, TokenType.SPACE))
            elif chr in cls.nums:
                tokens.append(Token(chr, TokenType.NUM))
            elif chr == cls.dot:
                tokens.append(Token(chr, TokenType.DOT))
            elif chr in cls.e:
                tokens.append(Token(chr, TokenType.E))
            elif chr in cls.plusminus:
                tokens.append(Token(chr, TokenType.PLUSMINUS))
            elif chr == cls.semicolon:
                tokens.append(Token(chr, TokenType.SEMICOLON))
            else:
                raise LexerError(chr)
        return tokens


class TokenReader():
    def __init__(self, tokens) -> None:
        self.tokens = tokens
        self.position = -1
    
    def read(self):
        self.position += 1
        return self.tokens[self.position]
    
    def peek(self):
        return self.tokens[self.position+1]
    
    def set_position(self, position):
        self.position = position


class Syntaxer():
    def __init__(self, tokens) -> None:
        self.tokenReader = TokenReader(tokens)
    
    def program(self):
        token = self.tokenReader.peek()
        while token.tipe is TokenType.SPACE:
            self.tokenReader.read()
            token = self.tokenReader.peek()

        position = self.tokenReader.position
        if not self.floatl():
            self.tokenReader.set_position(position)
            if not self.intl():
                return False
        
        token = self.tokenReader.peek()
        if token.tipe is TokenType.E:
            self.tokenReader.read()
            if not self.intl():
                return False
        
        token = self.tokenReader.peek()
        while token.tipe is TokenType.SPACE:
            self.tokenReader.read()
            token = self.tokenReader.peek()
        
        if self.tokenReader.peek().tipe is TokenType.SEMICOLON:
            self.tokenReader.read()
            if self.tokenReader.position == len(self.tokenReader.tokens)-1:
                return True
        
        return False

    def floatl(self):
        token = self.tokenReader.peek()
        if token.tipe is TokenType.PLUSMINUS:
            self.tokenReader.read()
        
        return self.floatabs()

    def floatabs(self):
        preflag = 0
        token = self.tokenReader.peek()
        while token.tipe is TokenType.NUM:
            preflag = 1
            self.tokenReader.read()
            token = self.tokenReader.peek()
        
        token = self.tokenReader.peek()
        if not (token.tipe is TokenType.DOT):
            return False
        
        self.tokenReader.read()

        suffflag = 0
        token = self.tokenReader.peek()
        while token.tipe is TokenType.NUM:
            suffflag = 1
            self.tokenReader.read()
            token = self.tokenReader.peek()
        
        if preflag or suffflag:
            return True
        else:
            return False

    
    def intl(self):
        token = self.tokenReader.peek()
        if token.tipe is TokenType.PLUSMINUS:
            self.tokenReader.read()
        
        return self.intabs()

    def intabs(self):
        flag = False
        token = self.tokenReader.peek()
        while token.tipe is TokenType.NUM:
            flag = True
            self.tokenReader.read()
            token = self.tokenReader.peek()
        return flag


if __name__ == '__main__':
    so = Solution()
    for s in ["+100", "5e2", "-123", "3.1416", "-1E-16", "0123 "]:
        print(so.isNumber(s))
    for s in ["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4", "1 "]:
        print(so.isNumber(s))
